package dproxy.server.net.access;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import dproxy.server.common.exceptions.AppInfrastructureException;
import dproxy.server.common.xml.to.IntermediareConfig;
import dproxy.server.concurrent.ThreadManager;
import dproxy.server.net.protocol.Protocol;

/**
 * One intermediare instace binds a network port and 
 * receive user requests.
 */
public class Intermediare implements Runnable {

    /** Logger. */
    private Log logger = LogFactory.getLog(getClass());
    /** Local socket that receives requests from user. */
    private ServerSocket serverSocket;
    /** Intermediare name [localhost:XX->remotehost:YY]. */
    private String name;
    /** Intermediare configuration. */
    private IntermediareConfig ic;
    
    /**
     * An intermediare opens a local socket and awaits user requests on this
     * port. The request content is going to be forwarded to the remote server.
     * When the server responds, this response is forwarded to the user.
     * Each intermediare runs in it's own thread.
     * 
     * @param ic Instance of the intermediare configuration
     */
    public Intermediare(IntermediareConfig ic) {
        try {
            name = "localhost:" + ic.getLocalPort() + "->" + ic.getRemoteHost() + ":" + ic.getRemotePort();
            this.ic = ic;
            
            // Set up local server socket
            serverSocket = new ServerSocket(ic.getLocalPort());
            
        } catch (IOException e) {
            throw new AppInfrastructureException(e);
        }
    }

    /**
     * Starts servicing requests.
     */
    public void start() {
        try {
            synchronized (this) {
                // Run this code on a different thread. 
                ThreadManager.getInstance()
                    .runInNewThread(this, name);
                this.wait();
            }
        } catch (InterruptedException e) {
            throw new AppInfrastructureException(e);
        }
    }
    
    
    /**
     * Closes this intermediare.
     */
    public void stop() {
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                logger.warn("Error closing " + name + " intermediare : " + e.getMessage());
            }
        } else {
            logger.debug("Application tried to stop a null server");
        }
    }
    
    /**
     * Loops receiving user requests.
     */
    public void run() {
        logger.info(
                "Starting Intermediare for protocol " 
                + ic.getProtocol().getName() 
                + " on " + ic.getLocalPort()
                + " port");
        synchronized (this) {
            this.notify();
        }
        while (true) {
            Socket userSocket;
            try {
                userSocket = serverSocket.accept();
                userSocket.setSoTimeout(Protocol.DEFAULT_SO_TIMEOUT);
            } catch (IOException e) {
                logger.info("Intermediare for protocol " 
                        + ic.getProtocol().getName() + " is no longer accepting connections");
                break;
            }
            Access access = new Access(userSocket,
                                       this.ic);
            ThreadManager.getInstance()
                .runInThreadFromPool(access);
        }
    }
}
